package edu.siu.math.egut.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;

import javax.swing.JFileChooser;

import com.sun.tools.javac.util.Pair;

import edu.siu.math.egut.util.Group;
import edu.siu.math.egut.util.NotARootException;
import edu.siu.math.egut.util.Polynomial;
import edu.siu.math.egut.util.PolynomialReadException;
import edu.siu.math.egut.util.Sunipotent;
import edu.siu.math.egut.util.Unipotent;

/**
 * Collection of static algorithms, related to I/O functions which are not 
 * part of the Egut interpreter.
 * 
 * @author Joseph Hundley
 *
 */
public class Algorithms {
   
    
   public static File getFile() {
	
	JFileChooser fc = new JFileChooser();
	//fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
	/* For now it seems desirable that only files should be 
	 * choosable.  Uncomment if that changes.
	 */

	int openClicked = fc.showOpenDialog(null);
	if(openClicked == JFileChooser.APPROVE_OPTION)
	    return fc.getSelectedFile();
	return null;
    }
   public static File getFile(File directory) {
	
	JFileChooser fc = new JFileChooser(directory);
	//fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
	/* For now it seems desirable that only files should be 
	 * choosable.  Uncomment if that changes.
	 */

	int openClicked = fc.showOpenDialog(null);
	if(openClicked == JFileChooser.APPROVE_OPTION)
	    return fc.getSelectedFile();
	return null;
   }

    
    /**
     * Some classes for interacting with the user in a 
     * fault tolerant way.
     * 
     */
    
    /**
     * @param i 
     * @return 
     * 
     */
    public static final Pattern suniString(int i){
	return Pattern.compile("x\\_\\{-?\\d{"+i+"}\\}\\(.*\\)");
    }
    
    /**
     * @param i 
     * @return 
     * 
     */
    public static final Pattern suniString(Group g){
	return Pattern.compile("x\\_\\{-?\\d{"+g.getRank()+"}\\}\\(.*\\)");
    }
    
    public static final Pattern weylWord(int i ){
	return Pattern.compile("\\[([1-"+i+"]\\,)*[1-"+i+"]\\]");
    }
    
    public static final Pattern weylWord(Group g){
	return weylWord(g.getRank());
    }
    
    /**
     * Get a polynomial from the user.
     * 
     * Used by Action and SectionIntegral but not DCA's... not checked FIXME
     * 
     * @param scanner a Scanner for user input
     * @return Polynomial parsed from user input
     */
    public static Polynomial getPoly(Scanner scanner){
	Polynomial p = Polynomial.create(1);
	boolean gotThePoly = false; 
	while( !gotThePoly){
	    gotThePoly = true;
	    try {

		String polyString =   scanner.nextLine().trim();
		while( polyString.equals(""))
		    polyString =   scanner.nextLine().trim();
		if(polyString.equalsIgnoreCase("quit"))
		    System.exit(1);
		p = Polynomial.read( polyString);
	    } catch (PolynomialReadException e) {
		System.out.print("Input could not be parsed into a polynomial.\n" +
				"Please try again, or type \"quit\" to exit.\n" +
				"This method has no \"bail\" option.");// FIXME 
		gotThePoly = false;
	    }
	}// end while not got the poly..

	return p;
	
    }// end getPoly
    public static Polynomial getPoly(String initialAttempt, Scanner scanner){
	Polynomial p = Polynomial.create(1);
	String polyString = initialAttempt;
	boolean gotThePoly = false; 
	while( !gotThePoly){
	    gotThePoly = true;
	    try {

		
		while( polyString.equals(""))
		    polyString =   scanner.nextLine().trim();
		if(polyString.equalsIgnoreCase("quit"))
		    System.exit(1);
		p = Polynomial.read( polyString);
	    } catch (PolynomialReadException e) {
		System.out.print("Input could not be parsed into a polynomial.  Please try again, or type \"quit\" to exit.");
		gotThePoly = false;
		polyString =   scanner.nextLine().trim();
	    }
	}// end while not got the poly..

	return p;
	
    }// end getPoly
    
    /**
     * Get a filename from the user.  This method takes user input, and checks whether it matches the 
     * name of an existing file.  If it does, this file name is returned.  If it does not, the user is 
     * prompted for further input.  In addition to correcting him/herself, the user has the opportunity
     * to "quit" (call System.exit(1)) or "bail" and return a value of "null" which the main program 
     * can interpret appropriately (say, as a decision to input data manually because the file from which
     * it was to be read could not be located).
     * 
     * Used by Action and SectionIntegral but not the DCA's not checked FIXME
     * 
     * @param scanner source of user input
     * @return the name of a file, as a String
     */
    public static String getFileName( final Scanner scanner){
	String filename =  scanner.nextLine();
	File file = new File(filename);
	
	// handle the relatively likely occurrences of mistyped or forgotten filenames.
	while (!file.exists()){
	    System.out.println("I could not find a file called \""+filename+".\"\nYou can try again, type \"quit\" to quit the program, or type \"bail\" to bail on \ninputting a file name without quitting the program.\n" +
	    		"(If you think your file is called \"quit\" or \"bail,\" I'm sorry, but \nI'm afraid that will be a problem.)");
	    filename = scanner.nextLine();
	    if(filename.trim().equals("quit"))
		System.exit(1);
	    else if(filename.trim().equals("bail"))
		return null;
	    else
		file = new File(filename);
	    
	    
	}// end while 
	
	return filename;
    }
    
    /**
     * Gets a root from user input.  This method accepts user input from a scanner,
     * it checks whether the input specifies a root of the given Group g.   If so,
     * the root is returned.  If not the user is given the opportunity to try again,
     * or to "bail" and return a value of "null."  
     * @param scanner for user input
     * @param g  a Group.  Input will be checked against the list of roots of this Group
     * @return a root, input by the user, or "null" to indicate that the user bailed.
     */
    public static int[] getRoot( final Scanner scanner, final Group g){
	
	/* The most convenient method for inputting roots of algebraic groups is 
	 * as integers.  The key point here is that when a root is expressed
	 * as a \Z-linear combination of simple roots, none of the coefficients 
	 * ever exceeds 9.  So, instead of writing out [2,2,3,4,3,2,1,0] (this is a 
	 * root of E8) one may safely write 22343210 without fear of creating ambiguity.  

	 *  Thus, the initial input from the user should be an Integer.
	 */
	boolean done = false;
	while( !done){
	int input = getInt(scanner);
	    /* convert the integer to a tuple of length equal to the rank of g.
	     * If the number of digits to the integer is less than the rank of g, leading 
	     * zeros are added.  If it exceeds the rank of g, then the extra digits
	     * AT THE BEGINNING are ignored.
	     */
	int[] root = rootFromInt(input, g.getRank());
	
	// if that tuple is a root of our Group g, then return it.
	if( g.isRoot(root))
	    return root;
	
	// if is equal to 0 (which is never a root) we interpret this as a desire on the 
	// users part to "bail."
	else if(input == 0)
	    done =true;
	// otherwise the input can not be interpreted.  Prompt for a correction.
	else
	    System.out.println("Does not match a root.  Please try again. (0 to bail.)\n");
	}
	return null;
    }
    
    
    /**
     * Some classes designed to allow egut to acquire lists of roots by reading LiE monfiles.
     */
    
    /**
     * Convert a file into one big String.
     * @param filename  the name of the file to be read
     * @return a String containing the contents of the file
     * @throws FileNotFoundException if the file is not found.
     * 
     */
    public static String fileToString( String filename) throws FileNotFoundException{
	final Scanner input;
	try {
	    input = new Scanner(new File(filename));
	} catch (FileNotFoundException fnf) {
	    System.out.printf("file not found.");
	    throw fnf;

	}// end catch
	
	
	// concatenate all file contents into a big string
	StringBuffer sb = new StringBuffer();

	while (input.hasNext()) {
	    sb.append(input.next().trim());
	}// end while
	String s = sb.toString();
	return s;
    }
    
   /**
    * Extracts a matrix -- formatted as it would be in LiE output, from a String.
    * The string may contain other things before and/or after the LiE matrix.  If the 
    * string contains more than one LiE matrix, this method should locate and 
    * return the first. 
    *
    * An error will be printed and null will be returned if any of the following occurs.
    * 	(1) the file does not have the form ... [[ ... ]] ... (i.e., no "matrix" is found)
    *   (2) the matrix has zero rows -- i.e., is [[]].
    *   (3) the rows of the matrix are of unequal lengths, or 
    *   (4) the rows are length zero (i.e., []).
    * @param s a String, which should contain a matrix, formatted as a bracketed list of bracketed lists, as in LiE output.
    * @return an int[][] version of the first such matrix appearing in the String s.
    */
public static int[][] readLiEMatrix( String s ) {
	
	int[][] m = null;
	
	
	/*
	 * This method will look for a LiE output formatted matrix in the
	 * file and ignore everything else.
	 */

	// String should have format ...[[...]]....
	s=s.replaceAll("\\[\\s*", "[").replaceAll("\\]\\s*", "]");
	int beginningOfMatrix = s.indexOf("[[")+2;
	int endOfMatrix = s.indexOf("]]");
	
	try {

	    if (beginningOfMatrix > -1
		    && endOfMatrix > beginningOfMatrix) {
		String firstMatrix = s.substring(
			beginningOfMatrix, endOfMatrix);
		String[] rows = firstMatrix.split("(\\s*)?\\](\\s*)?,(\\s*)?\\[(\\s*)?");
		int numRows = rows.length;
		    
		if (numRows > 0) {
		    int numCols = rows[0].split("(\\s*)?,(\\s*)?").length;
		    if( numCols > 0 ){
			 m = new int[numRows][numCols];
			for( int i = 0; i < numRows; i++)
			{
			    String[] row = rows[i].split("(\\s*)?,(\\s*)?");
			    if(row.length !=numCols )
				throw new LiEMatrixReadException();
			    for( int j = 0; j < numCols; j++)
				m[i][j] = Integer.parseInt(row[j].trim());
			}
			
		    }else 
			throw new LiEMatrixReadException("numCols <=0");

		    
		}// end if numRows > 0
		else
		    throw new LiEMatrixReadException("numRows <= 0");

	    }// end if beginningOfFirstMatrix >-1, and endOfMatrix > beginningOfMatrix
	    else
	    {
		System.out.println("beginning of Matrix = " + beginningOfMatrix);
		System.out.println("beginningOfMatrix > -1" + ( beginningOfMatrix > -1 ));
		System.out.println("end of matrix"+ endOfMatrix);
		
		throw new LiEMatrixReadException("beginning not found.");
		
	    }
	}/* end try block */catch (Exception e) { // will catch either the LiEMatrixReadExceptions thrown above
	    System.out.println("reading failed.");// or number format exceptions from ParseInt.
	    e.printStackTrace();
	    e.getCause().printStackTrace();
	    System.out.println(e.getCause().getMessage());

	}// end catch
	
	return m;
	
	
    }// end method readLiEMatrix ( String ) 

/**
 * Extracts a matrix -- formatted as it would be in LiE output, from a String.
 * The matrix must occur in the substring beginning at the integer "startingPoint."
 * The string may contain other things before and/or after the LiE matrix.  If the 
 * string contains more than one LiE matrix, this method should locate and 
 * return the first which begins at or after startingPoint.
 * 
*  
* @param s  a String, which should contain a matrix, formatted as a bracketed list of bracketed lists, as in LiE output, 
* which begins at or after startingPoint
 *@param startingPoint  an integer, which indicates where to start looking for the matrix.
 *
* @return an int[][] version of the first such matrix appearing in the String s.
* @throws Exception if (1) the file does not have the form ... [[ ... ]] ... (i.e., no "matrix" is found)
*                      (2) the matrix has zero rows -- i.e., is [[]].
*                      (3) the rows of the matrix are of unequal lengths, or 
*                      (4) any of the rows is length zero (i.e., []).
*                     TODO use custom exceptions instead of just Exception class.
*                     Do something to enable being able to tell which of the four potential problems occurred.
*/
   public static int[][] readLiEMatrix( String s, int startingPoint ) throws Exception {
	
	int[][] m = new int[1][1];
	
	
	/*
	 * This program will look for a LiE output formatted matrix in the
	 * file and ignore everything else.
	 */

	// file should have format ...[[...]]....
	int beginningOfMatrix = s.indexOf("[[", startingPoint) + 2;
	int endOfMatrix = s.indexOf("]]", startingPoint);
	try {

	    if (beginningOfMatrix > -1
		    && endOfMatrix > beginningOfMatrix) {
		String firstMatrix = s.substring(
			beginningOfMatrix, endOfMatrix);
		String[] rows = firstMatrix.split("(\\s*)?\\](\\s*)?,(\\s*)?\\[(\\s*)?");
		int numRows = rows.length;
		    
		if (numRows > 0) {
		    int numCols = rows[0].split("(\\s*)?,(\\s*)?").length;
		    if( numCols > 0 ){
			 m = new int[numRows][numCols];
			for( int i = 0; i < numRows; i++)
			{
			    String[] row = rows[i].split("(\\s*)?,(\\s*)?");
			    for( int j = 0; j < numCols; j++)
				m[i][j] = Integer.parseInt(row[j]);
			}
			
		    }else 
			throw new Exception("numCols <= 0");

		    
		}// end if numRows > 0
		else
		    throw new Exception("numRows <= 0");

	    }// end if beginningOfFirstMatrix >-1 ...
	    else
		throw new Exception("no Matrix found");
	}/* end try block */catch (Exception e) {
	    System.out.println("reading failed.");
	    e.printStackTrace();
	    throw e;

	}// end catch
	
	return m;
	
	
   }// end method readLiEMatrix ( String, int) 

/**
 * Header to be written to a TeX file.
 */
@SuppressWarnings("deprecation")
public static final String TEX_HEADER = "\\documentclass[12pt]{amsart}\n" +
		"\\usepackage{fullpage}\n" +
		"\\newcommand{\\ord}{\\operatorname{ord}}\n" +
		"\\begin{document}\n" +
		"This  file was automatically generated by " +
		"the Egut package ";
public static final String TIME_STAMP = Calendar.getInstance().getTime().toLocaleString()+".\n";
/**
 * Footer to be written to TeX files.
 */
public static final String TEX_FOOTER = "\\end{document}";
private static final String GETMATRIX_MENU = "Read [F]ile\t [I]nput Manually\t[Q]uit";
private static final char[] GETMATRIX_MENU_OPTIONS = {'F','f','I','i','Q','q'};
//private static final String GETMATRIXFROMFILE_MENU = "Set read order: [T]op to bottom(default) or [B]ottom to top, or [I]nput filename.";
//private static final char[] GETMATRIXFROMFILE_MENU_OPTIONS = {'T','t','B','b','I','i'};

/**
 * Parse an integer into an int[] of the given size.
 * (This is the most convenient way to input roots of exceptional groups by hand.
 * We are exploiting the fact that when a root is written as a linear combination of the 
 * simple roots, each of the coefficients is a single-digit number.)
 * 
 * (Examples:  11 and 2 gives [1,1]; 321 and 2 gives [2,1]; 11 and 8 gives [0,0,0,0,0,0,1,1].)
 * 
 * 
 * @param input the int to be parsed
 * @param rank the size for the parsed int[]
 * @return the parsed an int[] of size rank which contains the last rank digits of input
 */
public static int[] rootFromInt(int input, final int rank){
    int[] root = new int[rank];
    for( int i = 1; i <= rank; i++){
        root[ rank-i] = input % 10;
        input = input/10;
    }
    return root;
    
}

/**
 * Convert a tuple to a String, with brackets indicating the beginning and end, and commas 
 * separating elements.
 * 
 * Example: [2, 2, 3, 4, 3, 2, 1, 0].
 * 
 * @param a an int []
 * @return a String representation of a.
 */
public static String tupleToString(final int a[]){
    StringBuffer sb = new StringBuffer("[");
    for( int i =0; i < a.length; i++ ){
	sb.append(a[i]);
	if(i< a.length-1)
	    sb.append(", ");
	
	
    }
    sb.append("]");
    return sb.toString();
}

/**
 * Get a response to a yes or no question.  If initial user response can not be understood, prompt 
 * for correction.
 * @param scanner a Scanner for user input
 * @return true for "Yes" or false for "No."
 */
public static boolean getYesOrNo(final Scanner scanner) {
    boolean done = false ;
    while( ! done){
	String response = scanner.nextLine();
	if(response.matches("Y")|| response.matches("y")||response.matches("Yes")|| response.matches("yes"))
	    return true;
	if(response.matches("N")||response.matches("n")|| response.matches("no")|| response.matches("No"))
	    return false;
	System.out.println("Sorry.  I could not understand that.  Please input  \"Y\" or \"N\" ");
    }
    return false;
}

/**
 * Get an integer from user input.  If user input can be interpreted as an integer using 
 * Integer.parseInt(String) then its value is returned. If not, user is prompted
 * for more input.
 * 
 * TODO it might be nice to have "quit" and/or "bail on this mode of input" options.
 * 
 * @param scanner Scanner for user input.
 * @return Integer input
 */
public static int getInt(final Scanner scanner) {
    int answer = 0;
    boolean done = false;
    while(!done){
	done = true;
    try{
	answer = Integer.parseInt(scanner.nextLine());
    }
    catch(NumberFormatException e){
	System.out.println("Input appears not to be an integer.  Please try again.");
	done = false;
    }
    
    }
    return answer; 
 }


/**
 * Checks whether a given int[][] contains a given int[].
 * 
 * An int[i][j] can be viewed as a list of j int[i]'s.  This 
 * method returns true if the first argument (an int[][]) 
 * contains the second argument (an int[]) as one of its 
 * elements, when viewed as a list in this way.
 * 
 * (int[]'s are compared using Arrays.equals() )
 * 
 * @param list  an int[][], to be viewed as a list of int[]'s.
 * @param element an int[]
 * @return true if the first argument contains the second and false otherwise.
 */
public static boolean containsValue(final int[][] list, final int[] element) {
 
    for( int i = 0; i < list.length; i++)
	if( Arrays.equals(element,list[i]))
	    return true;
    return false;
}

/**
 * Converts an int[] to a short string by simply concatenating the String representations 
 * of its entries.
 * 
 * Example: {1,2,3,4} would become "1234", as would {12,34} and {123,4}.
 * 
 * This method of display is appropriate for positive roots of exceptional groups, since
 * it is known that no entry will have more than one digit.  
 * 
 * For tuples with negative entries, this method produces output which is a bit difficult 
 * to read.
 * 
 * 
 * 
 * @param a an int[]
 * @return a String with the elements of a run together.
 */
public static String tupleToShortString(final int[] a) {
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < a.length; i++)
	sb.append(a[i]);
    return sb.toString();
    
  
}

/**
 * Get a matrix (i.e., an int[][]) from the user, either manually or from a file.
 * 
 * This method manages the process of getting a matrix (i.e., an int[][]) from the 
 * user.  Currently, two methods are supported:  the user can input the int[][]
 * manually, or s/he can point to a file in which it is contained.  
 * @param scanner Scanner for user input
 * @return an int[][]
 */
/* 
 * Used by Action and SectionIntegral but not DCA's.  Not checked FIXME
 */

public static int[][] getMatrix(final Scanner scanner){
    System.out.println(GETMATRIX_MENU);
    char response = getMenuAnswer(GETMATRIX_MENU_OPTIONS, scanner );
    if( response =='Q'|| response =='q')
	System.exit(1);
    else if( response == 'F'|| response == 'f')
	return getMatrixFromFile( scanner);
    else if( response == 'I'|| response == 'i')
	return getMatrixFromInput( scanner);
    return null;
	  
    
}

private static int[][] getMatrixFromInput(Scanner scanner) {
    // TODO Auto-generated method stub
    return null;
}

/* 
 * Used by Action and SectionIntegral but not DCA's.  Not checked FIXME
 */

private static int[][] getMatrixFromFile(Scanner scanner) {
  /*  boolean topToBottom = true;
    System.out.println(GETMATRIXFROMFILE_MENU);
    char response = getMenuAnswer( GETMATRIXFROMFILE_MENU_OPTIONS, scanner);
    while( response != 'I'&& response != 'i')
    {
	if( response == 't'|| response =='T')
	    topToBottom = true;
	else if( response == 'b'|| response =='B')
	    topToBottom = false;
	response = getMenuAnswer( GETMATRIXFROMFILE_MENU_OPTIONS, scanner);
	
    }*/
    System.out.print("File name:");
    String filename = getFileName( scanner);
    try {
	String filecontents = fileToString(filename);
	boolean selected = false;
	int position = 0;
	int[][] answer;
	while(!selected){
	    try {
		answer = readLiEMatrix(filecontents, position);
		System.out.println(Arrays.toString(answer)+"\nSelect?");
		if(getYesOrNo(scanner))
		    return answer;
		else
		    position = filecontents.indexOf("]]", position+2);
	    } catch (Exception e) {
		if( position == 0){
		    System.out.println( "No matrices found.");
		    selected = true;
		}
		else {
		    System.out.println("No more matrices found.");
		    selected =true;
		}
	    }
	    
	    
	}
	
    } catch (FileNotFoundException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
    }
    
    
    return null;
}

/**
 * Get a user response matching some element of a specified menu.  This method
 * manages the process of receiving user input, and checking it against a 
 * specified list of menu options which the main program should know what to 
 * do with.  If the user response is not something that the main program 
 * will know how to handle, the user is prompted for further input.
 * 
 * @param menuOptions a char[], specifying what user responses are OK to return
 * @param scanner for user input
 * @return a char from the given list
 */
public static char getMenuAnswer(final char[] menuOptions, final Scanner scanner) {
    
    char c = scanner.next().charAt(0);
    while(true){
	for(int i = 0; i < menuOptions.length; i++)
	    if(c== menuOptions[i])
		return c;
	System.out.print("Sorry, I could not understand that.  Menu options:");
	for( int i = 0; i < menuOptions.length; i++)
	    System.out.print(menuOptions[i]+" ");
	System.out.println();
	c = scanner.nextLine().charAt(0);
	   
    }
}

/**
 * Get a variable as user input.
 * 
 * FIXME should the criteria be sharpened?
 * Is the name good?  Perhaps we will, at some point, want a method which checks that 
 * the input matches a "known variable."
 * 
 * This method is used only by SectionIntegral and has not been checked.
 * 
 * @param scanner for user input
 * @return a String 
 */
public static String getVariable(final Scanner scanner) {

    String answer = scanner.nextLine().trim();
    while(Math.max(answer.indexOf("+"), Math.max(answer.indexOf("-"), Math.max(answer.indexOf("*"), answer.indexOf("^"))))!=-1){
	System.out.println("Input contains one of +,-,*,^, suggesting an attempt to input a " +
			"polynomial-- in fact, all that can be processed at the moment is a single variable." +
			"use of these four characters in variable names is bound to cause confusion.  Please" +
			"try again.");
	answer = scanner.nextLine().trim();
	
	
    }
    return answer;
}

/*We'll need to fix up what's below if we ever revert to the use of "Pieces."
 * 
 *However, that seems unlikely. 
 * 
 */
///**
// * Handles the process of assisting the user in locating a 
// * serialized Piece file. Returns the piece from that file, 
// * or the Piece passed to this method if the user bails.
// * 
// * @param scanner Used for user I/O
// * @param p a Piece, to which this method defaults if attempts to 
// * load a Piece from file fail.
// * @return a Piece.
// */
//public static SectionIntegralPiece getSavedPiece(Scanner scanner, SectionIntegralPiece p) {
//    
//    String filename = "NOT NULL";
//    while( filename != null ){
//	filename = getFileName(scanner,".ser");
//	if( filename!=null ){
//	     SectionIntegralPiece q = SectionIntegralPiece.loadPiece(filename);
//	     if( q != null) //null reflects loadPiece failure, most likely because the user input
//		 return q;// the name of a file which does exist but is not a serialized Piece.
//	     else{
//		 q = SectionIntegralPiece.loadPiece(filename+".ser");
//		 if( q!= null)
//		     return q;
//		 else if(filename.endsWith(".ser")){
//		     q = SectionIntegralPiece.loadPiece(filename.substring(0,filename.indexOf(".ser")));
//		     if( q!=null )
//			 return q;
//		 }
//		 else
//		     System.out.println("File could be found, but does not appear to \n" +
//		     		"contain a serialized Piece.  Try again, or \"quit\" to quit, " +
//		     		"or \"bail\" to bail.");
//	     }
//	}
//	
//    }
//    
//    return p;// if we bail on loading a Piece, return the "default" Piece passed
//    // as an argument of this method.
//}

/**
 * Gets a filename from the user, in a context where some ending or suffix is presumed.
 * 
 * @param scanner a Scanner for user input
 * @param suffix a suffix, which will be appended to whatever the user types
 * @return a  String which is the name of a file, or null if the user bails.
 */
public static String getFileName(final Scanner scanner, final String suffix) {
	String filename =  scanner.nextLine();
	if( filename.trim().matches("")){
		filename =  scanner.nextLine();
	}
	File file = new File(filename+suffix);
	if(!file.exists())
	    file = new File(filename);
	if( !file.exists() && filename.endsWith(suffix))
	    file = new File( filename.substring(0,filename.indexOf(suffix)));
	
	// handle the relatively likely occurrences of mistyped or forgotten filenames.
	while (!file.exists()){
	    if(filename.trim().equals("quit"))
		System.exit(1);
	    else if(filename.trim().equals("bail"))
		return null;
	    if(filename.trim().matches(""))
		filename =  scanner.nextLine();
	    else{
	    System.out.println("I could not find a file called \""+filename+".\"\nYou can try again, type \"quit\" to quit the program, or type \"bail\" to bail on \ninputting a file name without quitting the program.\n" +
	    		"(If you think your file is called \"quit\" or \"bail,\" I'm sorry, but \nI'm afraid that will be a problem.)");
	    filename = scanner.nextLine();
		file = new File(filename+suffix);
		if( !file.exists())
		    file = new File( filename);
		if( !file.exists() && filename.endsWith(suffix))
		    file = new File( filename.substring(0,filename.indexOf(suffix)));

	    }
	    
	}// end while 
	
	return filename;

}

/**
 * Get the number of a term in a specified object from EGUT class Unipotent
 * 
 * FIXME really need to document the guts of this one and give a more detailed description 
 * of what it's doing.
 * 
 * 
 * @param u an object from class Unipotent
 * @param scanner a Scanner for user input
 * @return an integer specifying a term from u, or -1 if the user bails.
 */
    public static int getTermNumber(final Unipotent u, final Scanner scanner) {
	int input = getInt(scanner);
	List<Pair<Integer, Sunipotent>> options = new ArrayList<Pair<Integer, Sunipotent>>();
	int[] r = rootFromInt(input, u.getGroup().getRank());
	boolean bailed = false;
	while (!bailed) {

	    if (u.getGroup().isRoot(r)) {
		for (int i = 0; i < u.getNumberOfTerms(); i++) {
		    if (u.get(i).getRoot() == r) {
			options.add(new Pair<Integer, Sunipotent>(i, u.get(i)));
		    }
		}
	    }
	    if (-1 < input && input < u.getNumberOfTerms())
		options.add(new Pair<Integer, Sunipotent>(input, u.get(input)));
	    if (options.size() == 1)
		return options.get(0).fst;
	    if (options.isEmpty()) {
		System.out.println("Could not understand input.  Bail?");
		bailed = getYesOrNo(scanner);
		if (!bailed) {
		    System.out.println("Try again.");
		    input = getInt(scanner);

		    r = rootFromInt(input, u.getGroup().getRank());

		}
	    } else {
		for (Pair<Integer, Sunipotent> p : options) {
		    System.out.print(Integer.toString(p.fst) + p.snd);
		    System.out.println();
		    return getInt(scanner);
		}
	    }

	}

	return -1;

    }

/**
 * Checks whether any two int[] "rows" of a given int[][] add up to another "row"
 * of the int[][]. 
 *
 * (Suppose that the rows of m indicate roots of some algebraic group, and that
 * these roots determine a unipotent subgroup U.  Then U is abelian 
 * if and only if abelian(m) == true.)
 * 
 * FIXME would this be faster if I added roots using the Cayley table?
 * And wouldn't that also have built in checking for these to actually 
 * be roots of something?
 *
 * @param m an int[][]
 * @return true if m has two distinct rows which add up to a row of m, and false otherwise
 */
public static boolean abelian(final int[][] m) {
    int[] a = new int[m[0].length];
    for( int i = 0; i < m.length; i++){
	for( int j = i+1; j < m.length; j++){
	    for( int k = 0 ; k < a.length; k++)
		a[k]=m[i][k]+m[j][k];
	    for( int k = 0 ; k < m.length; k++)
		if( Arrays.equals(a,m[k]))
		return false;
	    
	}
    }
    
    return true;
}

/**
 * This method returns a string representation 
 * of an int[][] which will be correctly 
 * read by LiE.
 * 
 * @param m an int[]
 * @return a string representation of m, compatible with LiE
 */
public static String writeLieMatrix( final int[][] m){
    if(m.length == 0)
	return("null(0,Lie_rank)");// FIXME 
    StringBuilder sb = new StringBuilder();
    sb.append("[");
    for( int i =0 ; i< m.length-1; i++){
	sb.append( tupleToString(m[i])+",");
	
    }
    
    sb.append(tupleToString(m[m.length - 1])+"]");
    return sb.toString();
}
public static String writeLieMatrix( final ArrayList<int[]> m){
    if(m.size() == 0)
	return("null(0,Lie_rank)");// FIXME 
    StringBuilder sb = new StringBuilder();
    sb.append("[");
    for( int i =0 ; i< m.size()-1; i++){
	sb.append( tupleToString(m.get(i))+",");
	
    }
    
    sb.append(tupleToString(m.get(m.size() - 1))+"]");
    return sb.toString();
}

public static int[] parseWord(String string, Group g) {

    string = string.trim();
    int[] w = new int[string.length()];
    for( int i = 0; i < string.length(); i++){
	int d = string.charAt(i)-48;
	if(d<0|| d>9) 
	    throw new NumberFormatException();
	else 
	    w[i]=d;
    }
    return w;
}

public static String removeWhiteSpaceCommasAndBrackets(String next) {
     return next.replaceAll("[\\s\\[\\],]", "");
}

/**
 * @param w a String
 * @param rank an integer
 * @param scanner a Scanner
 * @return a Weyl word in the group of the given rank, which is the one represented by w if w does indeed
 * represent a Weyl word; null if the user types "bail."  User can also type "quit" in which case System.exit(1) is 
 * called.
 */
public static String getWeylElement(String w, int rank, Scanner scanner) {
    boolean done = false;
    if( w.isEmpty())
	w=removeWhiteSpaceCommasAndBrackets(scanner.nextLine());
    while(!done){
	if(edu.siu.math.egut.util.Algorithms.representsWeylWord(w, rank))
	    return w;
	else if(w.matches("quit"))
	    System.exit(1);
	else if(w.matches("bail"))
	    return null;
	System.out.println("Input \"" + w + "\" could not be parsed as Weyl Word in a group of rank "+rank +".\n" +
			"Please try again.");
	w=removeWhiteSpaceCommasAndBrackets(scanner.nextLine());
    }

    return null;
}

public static int getRank(Scanner scanner) {
    boolean done = false;
    while(! done){
	int rank = getInt(scanner);
	if(isTheRankOfAnExceptionalGroup(rank)){
	    return rank;
	}else{
	    System.out.println("Not the rank of a simple exceptional group.  (Your options are 2,4,6,7, and 8.)\n" +
	    		"Try again.");
	}
    }
    return 0;
}

private static boolean isTheRankOfAnExceptionalGroup(int rank) {
    return (rank==2||rank==4||(5<rank&&rank<9));
}

/**
 * TODO Give this method the ability to read negative roots as well.
 * @param string
 * @return
 * @throws IOException
 */
public static int[][] readHandTyped(String string) throws IOException {
    String[] roots = string.split("[,;:.]");
    if(roots.length==0)
	return null;
    int shift = roots[0].charAt(0)=='-'?1:0;
    
    int[][] answer =new int[roots.length][roots[0].length()-shift]; 
    for( int i = 0 ; i < roots.length; i++){
	if( roots[i].charAt(0)=='-')
	if( roots[i].length() != roots[0].length())
	    throw new IOException();
	
	for( int j = roots[i].length()-1 ; j > -1+shift ; j-- ){
	    if( (int) roots[i].charAt(j)< 48 || (int) roots[i].charAt(j)> 57)
		throw new IOException();
	    answer[i][j-shift]=(shift==1?-1:1)*(roots[i].charAt(j)-48);
	}
    }
    return answer;
}

/**
 * @param rootString
 * @param g
 * @return
 * @throws NumberFormatException 
 * @throws NotARootException 
 */
public static int[] rootFromString(String rootString) throws NumberFormatException, NotARootException {
     int shift = (rootString.charAt(0)=='-')?1:0;
     Group g = Group.getGroup(rootString.length()-shift);
    int[] answer = new int [rootString.length()-shift];
    for( int i = shift ; i < answer.length+shift; i++){
	if(isDigit(rootString.charAt(i)))
	    answer[i-shift] = value(rootString.charAt(i))*(shift==1?-1:1);
	else
	    throw new NumberFormatException();
    }
    if(!g.isRoot(answer))
	throw new NotARootException();
    return answer;

}
/**
 * @param rootString
 * @param g
 * @return
 * @throws NumberFormatException 
 * @throws NotARootException 
 */
public static int[] rootFromString(String rootString, Group g) throws NumberFormatException, NotARootException {
     int shift = (rootString.charAt(0)=='-')?1:0;
    int[] answer = new int [rootString.length()-shift];
    for( int i = shift ; i < rootString.length(); i++){
	if(isDigit(rootString.charAt(i)))
	    answer[i-shift] = value(rootString.charAt(i))*(shift==1?-1:1);
	else
	    throw new NumberFormatException();
    }
    if(!g.isRoot(answer))
	throw new NotARootException();
    return answer;

}

/**
 * @param charAt
 * @return
 */
public static int value(final char charAt) {
    return charAt-48;
}

/**
 * @param charAt
 * @return
 */
public static boolean isDigit(final char charAt) {
    return (charAt>47)&&(charAt<59);
}
/* 
 * Keep in mind:  the regexp may ignore whitespace but a Scanner will not.
 * FIXME implement a better method of parsing input.
 */
public static final Pattern SUNIPOTENT_OF_UNSPECIFIED_RANK = Pattern.compile("(x\\_\\{-?\\d{2}\\}\\(.*\\))|(x\\_\\{-?\\d{4}\\}\\(.*\\))|(x\\_\\{-?\\d{6,8}\\}\\(.*\\))");
public static final Pattern WEYL_ELEMENT = Pattern.compile("w?\\[(\\d,?\\s*)*\\]");
public static final Pattern GROUP_ELEMENT = Pattern.compile("("+SUNIPOTENT_OF_UNSPECIFIED_RANK.pattern()+")|("+WEYL_ELEMENT.pattern()+")");
public static final Pattern QUIT = Pattern.compile("q|(quit)|(exit)|(bye)", Pattern.CASE_INSENSITIVE);
public static final Pattern TEX_DOCUMENT_PATTERN = Pattern.compile("\\\\documentclass(\\[.*\\])?\\{.*\\}" +
".*\\\\begin\\{document\\}.*", Pattern.CASE_INSENSITIVE);

/**
 * @param string
 * @return
 * @throws SunipotentFormatException
 */
public static String sunipotentRootString(String string) throws SunipotentFormatException {
    if(string.indexOf('}') < 4)
	throw new SunipotentFormatException();
    return string.substring(3,string.indexOf('}'));
    
}

/**
 * @param string
 * @return
 * @throws SunipotentFormatException
 */
public static String sunipotentPolynomialString(String string) throws SunipotentFormatException {
    if(string.lastIndexOf(')') <= string.indexOf('('))
	throw new SunipotentFormatException();
    
    return string.substring(string.indexOf('(')+1, string.lastIndexOf(')'));
}

public static String makeRootList(String s) {
    return s.replaceAll("[\\s,]", "").replaceAll("(\\[\\[)","").replaceAll("(\\]\\])","").replaceAll("\\]\\[",",");
}

/**
 * @param next
 * @return
 */
public static Object shortenLiERoot(final String next) {
    return (next.contains("-")?"-":"")+next.replaceAll("[\\s\\[\\],-]", "");
}

/**
 * @param filename
 * @return
 * @throws FileNotFoundException
 */
public static String[] getCommandsFromFile(final String filename) throws FileNotFoundException {
    
	final Scanner input;
	try {
	    input = new Scanner(new File(filename));
	} catch (FileNotFoundException fnf) {
	    System.out.printf("file not found.");
	    throw fnf;

	}// end catch
	
	// collect the contents, first into an ArrayList of Strings
	ArrayList<String> strings=new ArrayList<String>();
	while (input.hasNext()) {
	    strings.add(input.next().trim());
	}// end while
	
	
	// convert to a String[] (to ease using this method to 
	// retrofit programs that used to use args).
	String[] s= new String[strings.size()];
	for( int i = 0; i < strings.size(); i++)
	    s[i]=strings.get(i);
	return s;
    
}

/**
 * @param a
 * @return
 */
public static String tupleToStringNoBrackets(final int[] a) {
    StringBuffer sb = new StringBuffer("");
    for( int i =0; i < a.length; i++ ){
	sb.append(a[i]);
	if(i< a.length-1)
	    sb.append(", ");
	
	
    }
    return sb.toString();
}

public static String[] getInput(String[] args) {
    String[] input;
    try {

        input = getCommandsFromFile(args[0]);
    } catch (FileNotFoundException e1) {
        System.out.println("File not found.  Please try again.");
        try {
    	input = getCommandsFromFile(getFileName(new Scanner(System.in)));
        } catch (FileNotFoundException e) {

    	input = new String[1];
    	System.out.println("File not found.  Please try again.");
    	e.printStackTrace();
    	System.exit(1);
        }
    }
    String[] newArgs = new String[input.length+args.length-1];
    for(int i = 0 ; i < input.length; i++)
        newArgs[i]=input[i];
    for(int i = 0 ; i < args.length -1; i++)
        newArgs[input.length+i]= args[1+i];
    return newArgs;
}

public static String getGroupName(Scanner scanner) {
    String attempt = scanner.nextLine();
    while(Group.getGroup(attempt) == null){
	System.out.println(attempt + " is not being recognized as the name of an exceptional group." +
			"Please try again.");
    }
    return attempt;
}

//TODO beef up this method so that the string can be in any sensible format.
    public static int[] tupleFromString(String string) {
	if (Pattern.compile("\\d+").matcher(string).matches()) {
	    int[] answer = new int[string.length()];
	    for (int i = 0; i < answer.length; i++) {
		if (isDigit(string.charAt(i)))
		    answer[i] = value(string.charAt(i));
		else
		    throw new NumberFormatException();
	    }
	    return answer;
	}
	if (Pattern.compile("\\-\\d+").matcher(string).matches()) {
	    int[] answer = new int[string.length() - 1];
	    for (int i = 1; i < answer.length; i++) {
		if (isDigit(string.charAt(i)))
		    answer[i-1] = -1 * value(string.charAt(i));
		else
		    throw new NumberFormatException();
	    }
	    return answer;
	}
	//if( Pattern.compile("\\s*\\[(\\s*\\-?\\s*\\d+\\s*,)+\\s*\\d+\\s*\\]\\s*").matcher(string).matches())
	throw new NumberFormatException();
    }

public static String removeWhiteSpace(String s) {

    return s.replaceAll("\\s", "");
}
public static void pause(int i) {
    long currentTime = System.nanoTime();
    
    long pauseTime = (long) (((long)i)*Math.pow(10, 9));
    while( System.nanoTime() < currentTime+pauseTime){
	
    }
    
	
    
}



}
